﻿using System.Collections;
using System.Collections.Generic;

namespace Charting
{
    public class DataPointCollection : ICollection<DataPoint>
    {
        private Series mSeries;
        private List<DataPoint> mPoints;

        internal DataPointCollection(Series series)
        {
            mSeries = series;
            mPoints = new List<DataPoint>();
        }

        public int Count
        {
            get { return mPoints.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public DataPoint this[int index]
        {
            get { return  mPoints[index]; }
            set
            {
                if (mPoints[index] != null)
                    mPoints[index].Series = null;
                mPoints[index] = value;
                if (mPoints[index] != null)
                    mPoints[index].Series = mSeries;
                UpdateSeries();
            }
        }

        public void Add(DataPoint item)
        {
            mPoints.Add(item);
            item.Series = mSeries;
            UpdateSeries();
        }

        public void Add(float x, float y)
        {
            DataPoint item = new DataPoint(x, y);
            mPoints.Add(item);
            item.Series = mSeries;
            UpdateSeries();
        }

        public void Clear()
        {
            foreach (DataPoint dp in mPoints)
                dp.Series = null;
            mPoints.Clear();
            UpdateSeries();
        }

        public bool Contains(DataPoint item)
        {
            return mPoints.Contains(item);
        }

        public void CopyTo(DataPoint[] array, int arrayIndex)
        {
            mPoints.CopyTo(array, arrayIndex);
        }

        public IEnumerator<DataPoint> GetEnumerator()
        {
            return mPoints.GetEnumerator();
        }

        public bool Remove(DataPoint item)
        {
            if (mPoints.Remove(item))
            {
                item.Series = null;
                UpdateSeries();
                return true;
            }
            return false;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return mPoints.GetEnumerator();
        }

        public DataPoint PointAt(float x)
        {
            if (mPoints == null)
                return DataPoint.Empty;
            if (mPoints.Count == 0)
                return DataPoint.Empty;

            int index = -1;
            FindInfo info = FindPointIndex(mPoints, x);
            if (info != FindInfo.Empty)
            {
                if (info.index == -1)
                {
                    if (info.leftIndex == -1)
                        index = info.rightIndex;
                    else if (info.rightIndex == -1)
                        index = info.leftIndex;
                    else
                    {
                        float interVal = (mPoints[info.leftIndex].X + mPoints[info.rightIndex].X) / 2;
                        if (x < interVal)
                            index = info.leftIndex;
                        else
                            index = info.rightIndex;
                    }
                }
                else
                    index = info.index;
            }

            if (index == -1)
                return DataPoint.Empty;

            return mPoints[index];
        }

        static FindInfo FindPointIndex(List<DataPoint> points, float x)
        {
            FindInfo info = new FindInfo();
            if (points.Count == 0)
                return FindInfo.Empty;

            int start = 0;
            int end = points.Count - 1;
            while (end >= start)
            {
                int middle = (start + end) / 2;
                if (x > points[middle].X)
                {
                    if (middle < end)
                    {
                        if (x <= points[middle + 1].X)
                        {
                            info.leftIndex = middle;
                            info.rightIndex = middle + 1;
                            if (x == points[middle + 1].X)
                                info.index = middle + 1;
                            else
                                info.index = -1;
                            return info;
                        }
                    }
                    else if (middle == end)
                    {
                        info.leftIndex = middle;
                        info.rightIndex = -1;
                        if (x == points[middle].X)
                            info.index = middle;
                        else
                            info.index = -1;
                        return info;
                    }

                    start = middle + 1;
                }
                else if (x < points[middle].X)
                {
                    if (middle > start)
                    {
                        if (x >= points[middle - 1].X)
                        {
                            info.leftIndex = middle - 1;
                            info.rightIndex = middle;
                            if (x == points[middle - 1].X)
                                info.index = middle - 1;
                            else
                                info.index = -1;
                            return info;
                        }
                    }
                    else if (middle == start)
                    {
                        info.leftIndex = -1;
                        info.rightIndex = middle;
                        if (x == points[middle].X)
                            info.index = middle;
                        else
                            info.index = -1;
                        return info;
                    }

                    end = middle - 1;
                }
                else
                {
                    if (middle == start)
                        info.leftIndex = -1;
                    else
                        info.leftIndex = middle - 1;
                    if (middle == end)
                        info.rightIndex = -1;
                    else
                        info.rightIndex = middle + 1;
                    info.index = middle;
                    return info;
                }
            }

            return FindInfo.Empty;
        }

        private void UpdateSeries()
        {
            mSeries.UpdatePoints();
            mSeries.ChartUpdateContent();
        }

        struct FindInfo
        {
            public static readonly FindInfo Empty = new FindInfo(-1, -1, -1);

            public int index;
            public int leftIndex;
            public int rightIndex;

            private FindInfo(int idx, int left, int right)
            {
                index = idx;
                leftIndex = left;
                rightIndex = right;
            }

            public override bool Equals(object obj)
            {
                return base.Equals(obj);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public override string ToString()
            {
                return base.ToString();
            }

            public static bool operator ==(FindInfo left, FindInfo right)
            {
                if (left.index == right.index
                    && left.leftIndex == right.leftIndex
                    && left.rightIndex == right.rightIndex)
                    return true;
                return false;
            }

            public static bool operator !=(FindInfo left, FindInfo right)
            {
                if (left.index != right.index
                    || left.leftIndex != right.leftIndex
                    || left.rightIndex != right.rightIndex)
                    return true;
                return false;
            }
        }
    }
}
